package com.apes.framework.jpa.spec.specification;

import lombok.Data;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Root;

/**
 * 功能：AbstractSpecification
 *
 * @author xul
 * @create 2018-07-10 15:15
 */
abstract class AbstractSpecification<T> implements Specification<T>, Serializable {

    public String getProperty(String property) {
        if (property.contains(".")) {
            return getProperty(StringUtils.split(property, ".")[1]);
        }
        return property;
    }

    public From getRoot(String property, Root<T> root) {
        JoinMate joinMate = join(new JoinMate(root, root, property));
        return joinMate.join;
    }

    public JoinMate join(JoinMate mate) {
        if (mate.getProperty().contains(".")) {
            String[] joinProperties = StringUtils.split(mate.property, ".");
            String joinProperty = joinProperties[0];
            //防止重复join
            Optional<Join<T, ?>> oldJoin = getJoins(mate.root).stream().filter(j -> j.getAttribute().getName().equals(joinProperty)).findAny();
            From join = oldJoin.orElseGet(() -> mate.join.join(joinProperty, JoinType.LEFT));
            return join(new JoinMate(mate.root, join, joinProperties[1]));
        }
        return mate;
    }

    private Set<Join<T, ?>> getJoins(From<T, ?> root) {
        Set<Join<T, ?>> joins = new HashSet<>();
        root.getJoins().stream().forEach(join -> {
            joins.add((Join<T, ?>) join);
            joins.addAll(getJoins((Join<T, T>) join));
        });
        return joins;
    }

    @Data
    private class JoinMate {
        private Root<T> root;
        private From join;
        private String property;

        public JoinMate(Root<T> root, From join, String property) {
            this.root = root;
            this.join = join;
            this.property = property;
        }
    }
}
